﻿#include "FITKGraphObjectBeamOrientationPreview.h"

// VTK
#include <vtkPoints.h>
#include <vtkIdList.h>
#include <vtkUnstructuredGrid.h>
#include <vtkProperty.h>
#include <vtkLabelPlacementMapper.h>
#include <vtkFreeTypeLabelRenderStrategy.h>
#include <vtkLabelHierarchy.h>
#include <vtkPointSetToLabelHierarchy.h>
#include <vtkTextProperty.h>
#include <vtkDistanceToCamera.h>
#include <vtkPlane.h>
#include <vtkDoubleArray.h>
#include <vtkPointData.h>

// Graph and filter
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKGraphActor2D.h"
#include "FITKGraphCommons.h"
#include "FITK_Interface/FITKVTKAlgorithm/FITKCoordinateAlgorithmEX.h"

// Render
#include "FITK_Component/FITKRenderWindowVTK/FITKGraphRender.h"

// Global data
#include "FITK_Kernel/FITKCore/FITKDataRepo.h"

// Data
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaBeamOrientation.h"
#include "FITK_Interface/FITKInterfaceModel/FITKModelSet.h"
#include "FITK_Component/FITKAbaqusData/FITKAbaqusPart.h"
#include "FITK_Interface/FITKInterfaceStructural/FITKAbaMeshPartVTK.h"

// Data manager
#include "FITKSetSurfPolyProvider.h"

namespace Exchange
{
    FITKGraphObjectBeamOrientationPreview::FITKGraphObjectBeamOrientationPreview(AbaqusData::FITKDataCase* caseData, Interface::FITKAbaBeamOrientation* beamOrient)
        : FITKGraphObjectFixedSizeBase(caseData, beamOrient)
    {
        if (!beamOrient)
        {
            return;
        }

        // Initialize the glyph size.
        m_glyphSize = 60.;

        // Initialize the pointer.
        init();

        // Generate the poly data for shape label.
        bool flag = generatePolyData(beamOrient);
        if (!flag)
        {
            return;
        }

        // Create actor and initialize properties.
        generateGraph();

        // Hide or show actors by the n1 state.
        updateVisibility();

        // Set the layer need to be rendered.
        setRenderLayer(1);
        
        // Save if the bounds are fixed or dynamic.
        m_hasFixedBounds = false;
    }

    FITKGraphObjectBeamOrientationPreview::~FITKGraphObjectBeamOrientationPreview()
    {
        // Delete data.
        deleteVtkObj(m_points);
        deleteVtkObj(m_gridAxes);
        deleteVtkObj(m_textPropertyT);
        deleteVtkObj(m_textProperty1);
        deleteVtkObj(m_textProperty2);
        deleteVtkObj(m_coordFilterT);
        deleteVtkObj(m_coordFilter1);
        deleteVtkObj(m_coordFilter2);
        deleteVtkObj(m_arrayDirT);
        deleteVtkObj(m_arrayDir1);
        deleteVtkObj(m_arrayDir2);
    }

    void FITKGraphObjectBeamOrientationPreview::init()
    {
        // Create the grid data.
        m_points = vtkPoints::New();
        m_gridAxes = vtkUnstructuredGrid::New();
        m_gridAxes->SetPoints(m_points);

        // Create the direction data array.
        //@{
        m_arrayDirT = vtkDoubleArray::New();
        m_arrayDir1 = vtkDoubleArray::New();
        m_arrayDir2 = vtkDoubleArray::New();
        m_arrayDirT->SetNumberOfComponents(3);
        m_arrayDir1->SetNumberOfComponents(3);
        m_arrayDir2->SetNumberOfComponents(3);
        m_arrayDirT->SetName(c_directionArrNameT);
        m_arrayDir1->SetName(c_directionArrName1);
        m_arrayDir2->SetName(c_directionArrName2);
        m_gridAxes->GetPointData()->AddArray(m_arrayDirT);
        m_gridAxes->GetPointData()->AddArray(m_arrayDir1);
        m_gridAxes->GetPointData()->AddArray(m_arrayDir2);
        //@}

        // Get the coordinate axes color.
        double color3_t[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_beamOrientColor_t, color3_t);
        double color3_1[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_beamOrientColor_1, color3_1);
        double color3_2[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_beamOrientColor_2, color3_2);

        // Text property for t-axes.
        m_textPropertyT = vtkTextProperty::New();
        m_textPropertyT->SetBold(false);
        m_textPropertyT->SetFontSize(20);
        m_textPropertyT->SetColor(color3_t);
        m_textPropertyT->SetFontFamilyToCourier();

        // Text property for 1-axes.
        m_textProperty1 = vtkTextProperty::New();
        m_textProperty1->SetBold(false);
        m_textProperty1->SetFontSize(20);
        m_textProperty1->SetColor(color3_1);
        m_textProperty1->SetFontFamilyToCourier();

        // Text property for 2-axes.
        m_textProperty2 = vtkTextProperty::New();
        m_textProperty2->SetBold(false);
        m_textProperty2->SetFontSize(20);
        m_textProperty2->SetColor(color3_2);
        m_textProperty2->SetFontFamilyToCourier();

        // Create the fix-sized filter.
        vtkDistanceToCamera* distToCamera = vtkDistanceToCamera::New();
        distToCamera->SetScreenSize(m_glyphSize);
        distToCamera->SetInputData(m_gridAxes);
        m_distToCameraList.push_back(distToCamera);

        // Create the axes creator for axes.
        m_coordFilterT = FITKCoordinateAlgorithmEX::New();
        m_coordFilterT->SetInputConnection(distToCamera->GetOutputPort());
        m_coordFilterT->SetLabel("t");
        m_coordFilterT->SetDirectionArrayName(c_directionArrNameT);

        m_coordFilter1 = FITKCoordinateAlgorithmEX::New();
        m_coordFilter1->SetInputConnection(distToCamera->GetOutputPort());
        m_coordFilter1->SetLabel("1");
        m_coordFilter1->SetDirectionArrayName(c_directionArrName1);

        m_coordFilter2 = FITKCoordinateAlgorithmEX::New();
        m_coordFilter2->SetInputConnection(distToCamera->GetOutputPort());
        m_coordFilter2->SetLabel("2");
        m_coordFilter2->SetDirectionArrayName(c_directionArrName2);

        // Add the point array name need to be used.      
#if VTK_MAJOR_VERSION < 8
        m_coordFilterT->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
        m_coordFilter1->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
        m_coordFilter2->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, "DistanceToCamera");
#else
        m_coordFilterT->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distToCamera->GetDistanceArrayName());
        m_coordFilter1->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distToCamera->GetDistanceArrayName());
        m_coordFilter2->SetInputArrayToProcess(0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_POINTS, distToCamera->GetDistanceArrayName());
#endif      
    }

    void FITKGraphObjectBeamOrientationPreview::initActor()
    {
        // Get the coordinate axes color.
        double color3_t[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_beamOrientColor_t, color3_t);
        double color3_1[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_beamOrientColor_1, color3_1);
        double color3_2[3] = { 0 ,0, 0 };
        FITKGraphCommons::QColorToDouble3(FITKGraphCommons::s_beamOrientColor_2, color3_2);

        // Create the actor for t-axes.
        m_fActorT = FITKGraphActor::New();
        m_fActorT->setGraphObject(this);
        m_fActorT->SetPickable(false);
        m_fActorT->setScalarVisibility(false);
        m_fActorT->GetProperty()->SetRepresentation(1);
        m_fActorT->GetProperty()->SetLineWidth(1);
        m_fActorT->GetProperty()->SetColor(color3_t);
        m_fActorT->setInputConnection(m_coordFilterT->GetOutputPort(0));
        addActor(m_fActorT);

        // Create the actor for 1-axes.
        m_fActor1 = FITKGraphActor::New();
        m_fActor1->setGraphObject(this);
        m_fActor1->SetPickable(false);
        m_fActor1->setScalarVisibility(false);
        m_fActor1->GetProperty()->SetRepresentation(1);
        m_fActor1->GetProperty()->SetLineWidth(1);
        m_fActor1->GetProperty()->SetColor(color3_1);
        m_fActor1->setInputConnection(m_coordFilter1->GetOutputPort(0));
        addActor(m_fActor1);

        // Create the actor for 2-axes.
        m_fActor2 = FITKGraphActor::New();
        m_fActor2->setGraphObject(this);
        m_fActor2->SetPickable(false);
        m_fActor2->setScalarVisibility(false);
        m_fActor2->GetProperty()->SetRepresentation(1);
        m_fActor2->GetProperty()->SetLineWidth(1);
        m_fActor2->GetProperty()->SetColor(color3_2);
        m_fActor2->setInputConnection(m_coordFilter2->GetOutputPort(0));
        addActor(m_fActor2);

        // Create actor 2D for t-axes.
        m_fActorLabel2DT = FITKGraphActor2D::New();
        m_fActorLabel2DT->SetPickable(false);
        m_fActorLabel2DT->setGraphObject(this);
        m_fActorLabel2DT->setActorType(ActorType2D::TextActor);

        // Create actor 2D for 1-axes.
        m_fActorLabel2D1 = FITKGraphActor2D::New();
        m_fActorLabel2D1->SetPickable(false);
        m_fActorLabel2D1->setGraphObject(this);
        m_fActorLabel2D1->setActorType(ActorType2D::TextActor);

        // Create actor 2D for 2-axes.
        m_fActorLabel2D2 = FITKGraphActor2D::New();
        m_fActorLabel2D2->SetPickable(false);
        m_fActorLabel2D2->setGraphObject(this);
        m_fActorLabel2D2->setActorType(ActorType2D::TextActor);

        // Text mapper and strategy.
        //@{
        QList<FITKCoordinateAlgorithmEX*> coordFilters{ m_coordFilterT, m_coordFilter1, m_coordFilter2 };
        QList<vtkTextProperty*> tpros{ m_textPropertyT, m_textProperty1, m_textProperty2 };
        QList<FITKGraphActor2D*> act2Ds{ m_fActorLabel2DT, m_fActorLabel2D1, m_fActorLabel2D2 };

        for (int i = 0; i < 3; i++)
        {
            vtkSmartPointer<vtkPointSetToLabelHierarchy> textData = vtkSmartPointer<vtkPointSetToLabelHierarchy>::New();
            textData->SetInputConnection(coordFilters[i]->GetOutputPort(1));
            textData->SetTargetLabelCount(1);
            textData->SetMaximumDepth(15);
            textData->SetLabelArrayName(coordFilters[i]->GetLabelArrayName());
            textData->SetTextProperty(tpros[i]);

            vtkSmartPointer<vtkLabelPlacementMapper> mapperText = vtkSmartPointer<vtkLabelPlacementMapper>::New();
            mapperText->UseDepthBufferOff();
            mapperText->SetShapeToNone();
            mapperText->SetStyleToOutline();
            mapperText->UseUnicodeStringsOff();

            vtkSmartPointer<vtkFreeTypeLabelRenderStrategy> strategyText = vtkSmartPointer<vtkFreeTypeLabelRenderStrategy>::New();

            // Set input connecton of label hierarchy and strategy.
            mapperText->SetInputConnection(textData->GetOutputPort());
            mapperText->SetRenderStrategy(strategyText);

            // Show all labels.
            mapperText->SetPlaceAllLabels(true);
            mapperText->SetMaximumLabelFraction(1.);
            mapperText->SetIteratorType(vtkLabelHierarchy::FULL_SORT);

            act2Ds[i]->SetMapper(mapperText);
        }
        //@}

        // Add actor2D.
        addActor(m_fActorLabel2DT);
        addActor(m_fActorLabel2D1);
        addActor(m_fActorLabel2D2);
    }

    void FITKGraphObjectBeamOrientationPreview::setVisible(bool visibility)
    {
        Interface::FITKAbaBeamOrientation* beamOrient = dynamic_cast<Interface::FITKAbaBeamOrientation*>(_dataObj);
        if (!beamOrient)
        {
            return;
        }

        // Get the n1 direction.
        std::array<double, 3> n1 = beamOrient->getN1Direction();

        // Check n1.
        bool hasN1 = !(qFuzzyCompare(0., n1[0]) && qFuzzyCompare(0., n1[1]) && qFuzzyCompare(0., n1[2]));

        // Set visibility for axes grid actor.
        //@{
        if (m_fActorT)
        {
            m_fActorT->SetVisibility(visibility);
        }

        if (m_fActor1)
        {
            m_fActor1->SetVisibility(visibility && hasN1);
        }

        if (m_fActor2)
        {
            m_fActor2->SetVisibility(visibility && hasN1);
        }
        //@}

        // Set visibility for axes label actor.
        //@{
        if (m_fActorLabel2DT)
        {
            m_fActorLabel2DT->SetVisibility(visibility && hasN1);
        }

        if (m_fActorLabel2D1)
        {
            m_fActorLabel2D1->SetVisibility(visibility && hasN1);
        }

        if (m_fActorLabel2D2)
        {
            m_fActorLabel2D2->SetVisibility(visibility && hasN1);
        }
        //@}
    }

    void FITKGraphObjectBeamOrientationPreview::update(bool forceUpdate)
    {
        // Update the visibility first.
        updateVisibility();

        Interface::FITKAbaBeamOrientation* beamOrient = dynamic_cast<Interface::FITKAbaBeamOrientation*>(_dataObj);
        if (!beamOrient)
        {
            return;
        }

        // Check if the n1 and set has changed.
        //@{
        // Get the component id.
        int setId = beamOrient->getModelSetID();

        // Get the n1 direction.
        std::array<double, 3> n1 = beamOrient->getN1Direction();

        // Check if the n1 has been created.
        bool hasN1 = !(qFuzzyCompare(0., n1[0]) && qFuzzyCompare(0., n1[1]) && qFuzzyCompare(0., n1[2]));

        if (m_setId == setId && m_hasN1 == hasN1 && !forceUpdate)
        {
            return;
        }
        //@}

        // Clear data.
        clearData();

        // Regenerate polydata.
        generatePolyData(beamOrient);

        // Modified actors.
        updateActors();
    }

    bool FITKGraphObjectBeamOrientationPreview::isRelatedTo(int dataObjId)
    {
        // Check if the id is the self-object id.
        bool isTheObj = FITKGraphObject3D::isRelatedTo(dataObjId);
        if (isTheObj)
        {
            return true;
        }

        // Check if the id is set id.
        if (m_setId == dataObjId)
        {
            return true;
        }

        return false;
    }

    bool FITKGraphObjectBeamOrientationPreview::generatePolyData(Interface::FITKAbaBeamOrientation* beamOrient)
    {
        if (!beamOrient)
        {
            return false;
        }

        // Get the component id.
        int setId = beamOrient->getModelSetID();

        // Check the set.
        bool isSetValid = getComponentValid(setId);
        if (!isSetValid)
        {
            return false;
        }

        // Get the set and mode( part ).
        Interface::FITKModelSet* set = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKModelSet>(setId);
        AbaqusData::FITKAbaqusPart* part = set->getAbstractModelAs<AbaqusData::FITKAbaqusPart>();
        if (!part)
        {
            return false;
        }

        Interface::FITKAbaMeshPartVTK* partMesh = part->getMesh();
        if (!partMesh)
        {
            return false;
        }

        // Get the vtk data of the part.
        vtkDataSet* partGrid = partMesh->getGrid();
        if (!partGrid)
        {
            return false;
        }

        // Get the n1 direction.
        std::array<double, 3> n1 = beamOrient->getN1Direction();

        // Check if the n1 has been created.
        bool hasN1 = !(qFuzzyCompare(0., n1[0]) && qFuzzyCompare(0., n1[1]) && qFuzzyCompare(0., n1[2]));

        // Generate the element set members.
        QList<int> elementIds;
        getPartEleSetMembers(set, elementIds);

        // P.S.
        // This set MUST be a comp-set with a node and an element set, or
        // a single element set. 

        // 1. Get the cell center of the grid.
        // 2. Calculate the set member's element info.
        bool flag = false;
        double fac = 10000.;

        if (hasN1)
        {
            for (const int & id : elementIds)
            {
                // Get the vtk element index.
                int vtkIndex = partMesh->getEleIndexByID(id);

                // Get the cell center.
                double pt[3]{ 0., 0., 0. };
                FITKGraphCommons::GetCellCenter(partGrid->GetCell(vtkIndex), pt);

                // Get t-direction.
                double tDir[3]{ 0., 0., 0. };
                flag = partMesh->getElementDirection(tDir, id);

                // Normalize.
                vtkMath::Normalize(tDir);

                // Get the real n1 direction.
                double ptN1[3]{ pt[0] + n1[0] * fac, pt[1] + n1[1] * fac, pt[2] + n1[2] * fac };
                double realN1[3]{ 0., 0., 0. };
                FITKGraphCommons::ProjectPointVector(pt, tDir, ptN1, realN1);

                // Get the n2 direction.
                double n2[3]{ 0., 0., 0. };
                FITKGraphCommons::GetPlaneNormal(pt, tDir, realN1, n2);

                // Add to direction array.
                m_arrayDirT->InsertNextTuple3(tDir[0], tDir[1], tDir[2]);
                m_arrayDir1->InsertNextTuple3(realN1[0], realN1[1], realN1[2]);
                m_arrayDir2->InsertNextTuple3(n2[0], n2[1], n2[2]);

                // Add the point.
                int ptId = m_points->InsertNextPoint(pt);
                Q_UNUSED(ptId);

                // Set the source type for t-axes.
                m_coordFilterT->SetUseCone(false);
                m_glyphSize = 60.;
                setScreenSize(m_glyphSize);

                //vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                //cell->InsertNextId(ptId);
                //m_gridAxes->InsertNextCell(VTKCellType::VTK_VERTEX, cell);
            }
        }
        else
        {
            for (const int & id : elementIds)
            {
                // Get the vtk element index.
                int vtkIndex = partMesh->getEleIndexByID(id);

                // Get the cell center.
                double pt[3]{ 0., 0., 0. };
                FITKGraphCommons::GetCellCenter(partGrid->GetCell(vtkIndex), pt);

                // Get t-direction.
                double tDir[3]{ 0., 0., 0. };
                flag = partMesh->getElementDirection(tDir, id);

                // Add to direction array.
                m_arrayDirT->InsertNextTuple3(tDir[0], tDir[1], tDir[2]);
                m_arrayDir1->InsertNextTuple3(0, 0, 1);
                m_arrayDir2->InsertNextTuple3(0, 0, 1);

                // Add the point.
                int ptId = m_points->InsertNextPoint(pt);
                Q_UNUSED(ptId);

                // Set the source type for t-axes.
                m_coordFilterT->SetUseCone(true);
                m_glyphSize = 45.;
                setScreenSize(m_glyphSize);

                //vtkSmartPointer<vtkIdList> cell = vtkSmartPointer<vtkIdList>::New();
                //cell->InsertNextId(ptId);
                //m_gridAxes->InsertNextCell(VTKCellType::VTK_VERTEX, cell);
            }
        } 

        // Save data.
        m_hasN1 = hasN1;
        m_setId = setId;

        return true;
    }

    void FITKGraphObjectBeamOrientationPreview::generateGraph()
    {
        // Create actors.
        initActor();
    }

    void FITKGraphObjectBeamOrientationPreview::clearData()
    {
        // Reset the input component grid.
        resetVtkObj(m_gridAxes);
        resetVtkObj(m_points);

        // Reset the data array for directions.
        resetVtkObj(m_arrayDirT);
        resetVtkObj(m_arrayDir1);
        resetVtkObj(m_arrayDir2);
    }

    void FITKGraphObjectBeamOrientationPreview::getPointByOriginAndDirection(double* origin, double* direc, double* pt)
    {
        if (!origin || !direc || !pt)
        {
            return;
        }

        double md = sqrt(pow(direc[0], 2) + pow(direc[1], 2) + pow(direc[2], 2));
        if (md == 0)
        {
            pt[0] = origin[0];
            pt[1] = origin[1];
            pt[2] = origin[2];
            return;
        }

        for (int i = 0; i < 3; i++)
        {
            pt[i] = origin[i] + direc[i] / md;
        }
    }

    bool FITKGraphObjectBeamOrientationPreview::getComponentValid(int compId)
    {
        // Check if the data exsits.
        Core::FITKAbstractDataObject* data = Core::FITKDataRepo::getInstance()->getDataByID(compId);
        if (!data)
        {
            return false;
        }

        // Check if the id is a set.
        Interface::FITKModelSet* set = Core::FITKDataRepo::getInstance()->getTDataByID<Interface::FITKModelSet>(compId);
        if (!set)
        {
            return false;
        }

        return true;
    }

    void FITKGraphObjectBeamOrientationPreview::getPartEleSetMembers(Interface::FITKModelSet* set, QList<int> & elementIds)
    {
        elementIds.clear();

        if (!set)
        {
            return;
        }

        // Get the sub-set number.
        int nSubSet = set->getDataCount();
        if (nSubSet == 0)
        {
            // Get set type.
            Interface::FITKModelEnum::FITKModelSetType type = set->getModelSetType();

            // Check the set type.
            if (type != Interface::FITKModelEnum::FITKModelSetType::FMSElem)
            {
                return;
            }

            // Generate sub-set poly data.
            getPartEleSetMembersPrivate(set, elementIds);
        }
        else
        {
            for (int i = 0; i < nSubSet; i++)
            {
                // Get the sub-set;
                Interface::FITKModelSet* subSet = set->getDataByIndex(i);
                if (!subSet)
                {
                    continue;
                }

                // Get set type.
                Interface::FITKModelEnum::FITKModelSetType type = subSet->getModelSetType();

                // Check the set type.
                if (type != Interface::FITKModelEnum::FITKModelSetType::FMSElem)
                {
                    continue;
                }

                // Generate sub-set poly data.
                getPartEleSetMembersPrivate(subSet, elementIds);

                return;
            }
        }
    }

    void FITKGraphObjectBeamOrientationPreview::getPartEleSetMembersPrivate(Interface::FITKModelSet* set, QList<int> & elementIds)
    {
        // Copy indice.
        const QList<int> ids = set->getAbsoluteMember();
        for (const int & id : ids)
        {
            elementIds.push_back(id);
        }
    }

#ifdef QT_DEBUG
    void FITKGraphObjectBeamOrientationPreview::simplified(bool enabled)
    {
        if (!m_graphSimplified)
        {
            return;
        }

        bool dataVisible = getDataVisibility();

        // Change the actor visibility while changing the camera.
        if (dataVisible)
        {
            if (m_fActorLabel2DT)
            {
                m_fActorLabel2DT->SetVisibility(!enabled);
            }

            if (m_fActorLabel2D1)
            {
                m_fActorLabel2D1->SetVisibility(!enabled);
            }

            if (m_fActorLabel2D2)
            {
                m_fActorLabel2D2->SetVisibility(!enabled);
            }

            if (m_fActorT)
            {
                m_fActorT->SetVisibility(!enabled);
            }

            if (m_fActor1)
            {
                m_fActor1->SetVisibility(!enabled);
            }

            if (m_fActor2)
            {
                m_fActor2->SetVisibility(!enabled);
            }
        }
    }
#endif // QT_DEBUG
}   // namespace Exchange